﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Security.Cryptography;
using System.Text;
using System.Web;
using Z.Nlayer.Infrastructure.Utility.BaseEnums;
using Z.Nlayer.Utility.Encrypt;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using HttpContext = Z.Nlayer.Utility.HttpContext;
using Z.Nlayer.Utility;
using Company.Project.Infrastructure.Common.CustomerAttribute;

namespace Company.Project.Infrastructure.Common
{
    /// <summary>
    /// 此类仅用于API接口校验参数
    /// </summary>
    public class DetectionParamHelper
    {
        /// <summary>
        /// 与调用方约定的KEY
        /// </summary>
        private static readonly string ConfigPrivatekey = ReadConfig.ReadAppSetting("MD5_KEY");

        private static readonly string[] Excep = { "sign", "timespan" };
        /// <summary>
        /// 主要函数，用于解密API参数并回调
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="md5Parmers"></param>
        /// <returns></returns>
        public static T Decrypt<T>(string md5Parmers) where T : new()
        {
            try
            {
                return CheckTimeSign<T>(CheckParam<T>(md5Parmers));
            }
            catch (CustomException ce)
            {
                throw ce;
            }
            catch (TargetInvocationException targetEx)
            {
                if (targetEx.GetBaseException().GetType() == typeof(CustomException))
                {
                    throw targetEx.GetBaseException();
                }
                throw new Exception("内部错误,请重试!");
            }
            catch (Exception excep)
            {
                LoggerHelper.Log("RSA/MD5解密错误:" + excep.Message + "\r\n内部错误:" + excep.InnerException);
                throw new Exception("内部错误,请重试!");
            }
        }
        /// <summary>
        /// 对参数进行基础校验
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="nativeObj"></param>
        /// <returns></returns>
        public static string CheckParam<T>(string nativeObj)
        {
            JObject parm = JsonConvert.DeserializeObject<JObject>(nativeObj.ToLower());
            var tparmPropers = typeof(T).GetProperties().ToList();
            var tparmName = tparmPropers.Select(x => x.Name.ToLower()).ToList();
            var nativeName = new List<string>();
            foreach (var item in parm)
            {
                nativeName.Add(item.Key.ToLower());
            }
            //首先校验参数是否多余
            var expectednative = nativeName.Except(tparmName).ToList();
            if (expectednative.Any())
            {
                throw new CustomException(Enum_ApiResultCode.CodeOutRange.GetValueToInt(),
                    $"{Enum_ApiResultCode.CodeOutRange.GetLocalizedDescription()},参数名:{string.Join(",",expectednative)}");
            }
            //然后校验参数BaseCustom
            tparmPropers.ForEach(x =>
            {
                if (HttpContext.Current.Request.IsOriginRequest() && Excep.Contains(x.Name.ToLower()))
                {
                    return;
                }
                var xName = x.Name.ToLower();
                var customAttr = (BaseCustomAttribute[])x.GetCustomAttributes(typeof(BaseCustomAttribute), false);
                //如果参数没有custom头,则并且不是虚字段,则抛接口异常
                if (customAttr.Length == 0 && !x.GetMethod.IsVirtual)
                {
                    throw new CustomException(Enum_ApiResultCode.OtherErr.GetValueToInt(),$"接口异常,参数名{xName}");
                }
                else
                {
                    //校验custom头
                    var childCheck = typeof(DetectionParamHelper).GetMethod("CheckParam");
                    if (customAttr.Length != 0)
                    {
                        var displayName = string.IsNullOrEmpty(customAttr[0].DisplayName)? xName : customAttr[0].DisplayName;
                        var maxLen = customAttr[0].MaxLen;
                        var minLen = customAttr[0].MinLen;
                        var nullValue = customAttr[0].NullValue;
                        var nullField = customAttr[0].NullField;
                        var values=new List<Tuple<Type, string>>();
                        if (!nullField)
                        {
                            if (parm.Property(xName) == null)
                            {
                                throw new CustomException(Enum_ApiResultCode.NullPropertyCodeErr.GetValueToInt(),
                                    $"{displayName}不能为空!");
                            }
                        }
                        if (x.PropertyType.Name == (typeof(List<>)).Name)
                        {
                            foreach (var obj in JsonConvert.DeserializeObject<JArray>(parm[xName].ToString()))
                            {
                                values.Add(new Tuple<Type, string>(x.PropertyType.GenericTypeArguments[0], obj.ToString()));
                            }
                        }
                        else
                        {
                            if (!nullField)
                            {
                                values.Add(new Tuple<Type, string>(x.PropertyType, parm[xName].ToString()));
                            }
                        }
                        values.ForEach(value =>
                        {
                            if (!nullField)
                            {
                                if (!nullValue)
                                {
                                    if (string.IsNullOrEmpty(value.Item2))
                                    {
                                        throw new CustomException(Enum_ApiResultCode.NullPropertyCodeErr.GetValueToInt(), $"{displayName}值不能为空!");
                                    }
                                    CheckValueType(value.Item1, displayName, value.Item2);
                                    if (minLen > 0 && value.Item2.Length < minLen)
                                    {
                                        throw new CustomException(Enum_ApiResultCode.CodeLenErr.GetValueToInt(), $"{displayName}值长度不能少于{minLen}位!");
                                    }
                                    if (maxLen > 0 && value.Item2.Length > maxLen)
                                    {
                                        throw new CustomException(Enum_ApiResultCode.CodeLenErr.GetValueToInt(), $"{displayName}值长度不能多于{maxLen}位!");
                                    }
                                }
                            }
                        });
                    }
                    //如果参数没有custom头,则并且是List<>则循环判断
                    else if (x.PropertyType.Name == (typeof(List<>)).Name)
                    {
                        var childnativeObj = JsonConvert.DeserializeObject<JArray>(parm[xName].ToString());
                        foreach (var obj in childnativeObj)
                        {
                            childCheck.MakeGenericMethod(x.PropertyType.GenericTypeArguments[0]).Invoke((new Z.Nlayer.Utility.Common()),
                                new object[] {JsonConvert.SerializeObject(obj)});
                        }
                    }
                    //如果参数没有custom头,则并且是T则判断
                    else if (x.PropertyType.GenericTypeArguments.Any())
                    {
                        childCheck.MakeGenericMethod(x.PropertyType).Invoke((new DetectionParamHelper()),
                            new object[] {parm[xName].ToString()});
                    }
                    else if (x.GetMethod.IsVirtual)
                    {
                        childCheck.MakeGenericMethod(x.PropertyType).Invoke((new DetectionParamHelper()),
                            new object[] { parm[xName].ToString() });
                    }
                    else
                    {
                        throw new CustomException(Enum_ApiResultCode.NullCodeErr.GetValueToInt(), $"接口异常,参数名{xName}");
                    }
                }
            });
            //忽略参数大小写
            return nativeObj.ToLower();
        }
        /// <summary>
        /// 时间错校验
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="nativeObj"></param>
        /// <returns></returns>
        public static T CheckTimeSign<T>(string nativeObj)
        {
            dynamic tobj = JsonConvert.DeserializeObject<T>(nativeObj);
            if (HttpContext.Current.Request.IsOriginRequest())
            {
                return tobj;
            }
            if (string.IsNullOrEmpty(tobj.Timespan))
            {
                throw new CustomException(Enum_ApiResultCode.TimeExpire.GetValueToInt(), Enum_ApiResultCode.TimeExpire.GetLocalizedDescription());
            }
            TimeSpan ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0);
            var span = Convert.ToInt64(ts.TotalSeconds);
            if (span - TConvert.toInt64(tobj.Timespan) > 60)
            {
                throw new CustomException(Enum_ApiResultCode.TimeExpire.GetValueToInt(), Enum_ApiResultCode.TimeExpire.GetLocalizedDescription());
            }
            else
            {
                JObject json = JObject.Parse(nativeObj);
                IDictionary<string, dynamic> parameters = new Dictionary<string, dynamic>();
                Excep.ToList().ForEach(x =>
                {
                    json.Remove(x);
                });
                foreach (var jItem in json)
                {
                    parameters.Add(jItem.Key, jItem.Value);
                }
                parameters.Add("timespan", tobj.Timespan);
                var sign = GetMd5Parm(parameters, ConfigPrivatekey, true);
                if (sign.ToLower() == tobj.Sign.ToLower())
                {
                    return tobj;
                }
                else
                {
                    throw new CustomException(Enum_ApiResultCode.SignCodeErr.GetValueToInt(), Enum_ApiResultCode.SignCodeErr.GetLocalizedDescription());
                }
            }
        }

        static void CheckValueType(Type type, string name, object value)
        {
            try
            {
                //单独处理guid
                if (type == typeof(Guid))
                {
                    Guid.Parse(value.ToString());
                }
                else
                {
                    Convert.ChangeType(value, type);
                }
            }
            catch (Exception)
            {
                throw new CustomException(Enum_ApiResultCode.CodeValErr.GetValueToInt(), $"{name}值类型必须为{type.Name}");
            }
        }
        /// <summary>
        /// 签名对比
        /// </summary>
        /// <param name="parameters"></param>
        /// <param name="secret"></param>
        /// <param name="qhs"></param>
        /// <returns></returns>
        public static string GetMd5Parm(IDictionary<string, dynamic> parameters, string secret, bool qhs)
        {
            // 第一步：把字典按Key的字母顺序排序
            IDictionary<string, dynamic> sortedParams = new SortedDictionary<string, dynamic>(parameters);
            IEnumerator<KeyValuePair<string, dynamic>> dem = sortedParams.GetEnumerator();

            // 第二步：把所有参数名和参数值串在一起
            StringBuilder query = new StringBuilder(secret);
            var tmpVal = "";
            while (dem.MoveNext())
            {
                string key = dem.Current.Key;
                dynamic value = dem.Current.Value;
                if (!string.IsNullOrEmpty(key) && value != null)
                {
                    //数组类型参数就用原始json字符串(需要注意生成签名数组元素的顺序与POST的json字符串要一致)
                    if (value.GetType().BaseType == typeof(object[]).BaseType ||
                        value.GetType().BaseType == typeof(JArray).BaseType)
                    {

                    }
                    else
                    {
                        if (key == "timespan")
                        {
                            tmpVal = value;
                        }
                        else
                        {
                            query.Append(key).Append(value);
                        }
                    }
                }
            }
            query.Append("timespan").Append(tmpVal);
            if (qhs)
            {
                query.Append(secret);
            }

            // 第三步：使用MD5加密
            MD5 md5 = MD5.Create();
            byte[] bytes = md5.ComputeHash(Encoding.UTF8.GetBytes(query.ToString()));

            // 第四步：把二进制转化为大写的十六进制
            StringBuilder result = new StringBuilder();
            for (int i = 0; i < bytes.Length; i++)
            {
                string hex = bytes[i].ToString("X");
                if (hex.Length == 1)
                {
                    result.Append("0");
                }
                result.Append(hex);
            }

            return result.ToString();
        }
    }
}
